<?php

/**
 * @class UAL_Router
 * @extends x10_GeneralClass
 *
 * <p>Az útvonalválasztásban kulcsszerepet játszó osztály. Első példányosításakor
 * dedikálja magát a rendszerben, kezeli az útvonalat és az útvonal elemek
 * léptetését. Leszármazottaival ellentétben önállóan nem alkalmas útvonal
 * választására.<br/>
 * Minden leszármazott útvonalválasztó a dedikált példány útvonalvezetékét
 * használja az általa kezelt útvonalelemek manipulációjakor.</p>
 *
 * <p>A rendszer útvonalválasztási megoldása rendkívül egyszerű de rendkívül
 * komplex útvonalszerkezetek kezelésére alkalmas: minden rendszerelem sajátmaga
 * gondoskodik a számára releváns útvonalkezelőkről. A rendszerindításkor lefutó
 * első útvonalválasztókat az {@configFile x10.config.json} konfigurációs fájl
 * {@configOption x10.config.json#routers routers} beállításával kell
 * meghatározni - itt egymás után több útvonalválasztó osztály neve is
 * felsorolható.<br/>
 * Az elsőként sikeresen példányosító útvonalválasztó által létrehozott objektum
 * maga dönti el, hogy az útvonalvezetékben maradt elemeket átadja-e kezelésre
 * további útvonalválasztóknak, avagy nem. Így tetszőleges mélységig építhetőek
 * egymásba alkalmazások, vagy útvonal álnevek - például.</p>
 *
 * @author prometheus
 */
class UAL_Router extends x10_GeneralClass
{
	public $xName = 'Rendszerszintű útvonalkezelő';

	/**
	 * A rendszer által használt útvonalvezeték.
	 * @var Array
	 */
	public $routeQueue = array();

	/**
	 * Az útvonalvezeték eredetije.
	 * @var Array
	 */
	public $originalRouteQueue = array();

	/**
	 * Az útvonalvezeték elejéről utoljára kiléptetett elem.
	 * @var String
	 */
	public $shifted = '';

	/**
	 * Az útvonalból kiléptetett elemeket tároló nem valós verem.
	 * Amikor üres útvonalvezetékből léptetünk ki elemet, a verembe NULL kerül.
	 * @var Array
	 */
	public $shiftStack = array();

	/**
	 * Az útvonalvezeték másikra cserélésének műveleti verme.
	 * Amikor az útvonalvezetéket egy másikra cseréli a fejlesztő, a régi
	 * útvonal ebbe a verembe kerül.
	 * @var Array
	 */
	public $replaceStack = array();

	/**
	 * Műveleti verem a léptetési és csere műveletek visszavonásához.
	 * A verembe kerülő elemek az utolsó művelet visszavonásához használható
	 * metódus nevét tartalmazza.
	 * @var Array
	 */
	public $operationStack = array();

	/**
	 * Az osztály dedikált példánya.
	 * @var UAL_Router
	 */
	protected $router;

	/**
	 * Értéke lehet 'default' vagy 'strict'. Strict módban az útvonalválasztás
	 * művelete valamilyen hibával le fog állni (a hiba megjelenítése az adott
	 * útvonalválasztótól függ) amennyiben az adott útvonalat az útvonalválasztó
	 * objektum nem képes feldolgozni.
	 * @var String
	 */
	protected $mode = 'default';

	public function __construct(x10_Config $config=NULL)
	{
		parent::__construct($config);

		// Csak az UAL_Router osztályt dedikáltatjuk, a leszármazottakat NEM.
		if (get_class($this) == 'UAL_Router' and !x10::isDedicatedObjectExists('UAL_Router'))
		{
			x10::ifDedicated($this);
			x10::dedicate($this);
			$this->router = $this;

			$this->originalRouteQueue = $this->parseUserQuery();
			$this->routeQueue = $this->originalRouteQueue;
			if (count($this->routeQueue) == 0) $this->isQueueEmpty = TRUE;
		}
		else
		{
			$this->router = x10::dedicate('UAL_Router');
		}

		$mode = $this->config->getConfigKey('mode');
		if (!isset($mode)) $this->mode = $mode;
	}

	/**
	 * Megtisztítja a felhasználó vagy a fejlesztő által megadott (perjelekkel
	 * tagolt) útvonalat és visszaadja tömbként.
	 * @param String $opcQuery
	 * @return Array
	 */
	protected function parseUserQuery($opcQuery=FALSE)
	{
		$result = array();
		$userQuery = '';
		if ($opcQuery === FALSE)
		{
			$req = x10::dedicate('UAL_Request');
			$userInput = $req[$req->method]->get()->arguments;
			if (isset($userInput['q'])) $userQuery = $userInput['q'];
		}
		else
		{
			$userQuery = $opcQuery;
		}
		if (!empty($userQuery) and $userQuery{0} == '/') $userQuery = substr($userQuery, 1);
		if (!empty($userQuery))
		{
			$result = explode('/', $userQuery);
		}

		return $result;
	}

	/**
	 * Kilépteti az útvonalvezeték első elemét, amennyiben a vezeték nem üres,
	 * majd a kiléptetett elemet eltárolja a {@link #shifted} tulajdonságban és
	 * ennek értékével tér vissza. Amikor a vezeték üres, a visszatérés üres
	 * karakterlánc lesz.
	 * @return String
	 */
	public function shiftQueue()
	{
		if (count($this->router->routeQueue) > 0)
		{
			$this->shifted = array_shift($this->router->routeQueue);
			array_push($this->router->shiftStack, $this->shifted);
			array_push($this->router->operationStack, 'cancelShift');
		}
		else
		{
			$this->shifted = '';
			array_push($this->router->shiftStack, NULL);
			array_push($this->router->operationStack, 'cancelShift');
		}
		return $this->shifted;
	}

	public function getShifted()
	{
		return $this->shifted;
	}

	/**
	 * Visszavonja az utolsó kiléptetést.
	 */
	protected function cancelShift()
	{
		$item = NULL;
		if (count($this->router->shiftStack) > 0)
		{
			$item = array_pop($this->router->shiftStack);
		}
		if (!is_null($item))
		{
			array_unshift($this->router->routeQueue, $item);
		}
	}

	/**
	 * Felülírja az aktuális útvonalvezetéket a paraméterben megadottal.
	 * @param Array $with
	 */
	public function replaceQueue($with)
	{
		array_push($this->router->replaceStack, $with);
		array_push($this->router->operationStack, 'cancelReplace');
		$this->router->routeQueue = $with;
	}

	/**
	 * Visszaállítja a legutóbb felülírt útvonalvezetéket.
	 */
	protected function cancelReplace()
	{
		$item = NULL;
		if (count($this->router->replaceStack) > 0)
		{
			$item = array_pop($this->router->replaceStack);
		}
		if (!is_null($item))
		{
			$this->router->routeQueue = $item;
		}
	}

	/**
	 * Visszavonja az utoljára végzett kiléptetési- vagy felülírási műveletet.
	 */
	public function cancelOperation()
	{
		if (count($this->router->operationStack) > 0)
		{
			$method = array_pop($this->router->operationStack);
			$this->$method();
		}
	}

	/**
	 * Visszaadja az aktuális útvonalvezeték tartalmát.
	 * @return Array
	 */
	public function getQueue()
	{
		return array_merge($this->router->routeQueue);
	}

	/**
	 * Ellenőrzi, hogy az adott elemnévből képzett osztály létezik-e.
	 * @param String $itemName
	 * @return Bool
	 */
	public function exists($itemName)
	{
		return class_exists($this->getClassNameFor($itemName));
	}

	/**
	 * Egyes menedzserek számára készült metódus, amely visszaadja a router
	 * által kezelt elemek listáját. A lista elemeinek típusa tehát minden
	 * esetben az adott útvonalválasztó típusától (osztályától) függ.
	 * @return Array
	 */
	public function getList()
	{
		return array();
	}

	/**
	 * @abstract
	 * Visszaadja az elemnévből képzett osztálynevet.
	 * @param String $itemName
	 * @return String
	 */
	public function getClassNameFor($itemName)
	{
		
	}
	
	/**
	 * @abstract
	 * Elvégzi az adott útvonalvezeték célszerinti kezelését.
	 * @return Mixed Amennyiben visszatérése NULL, a kezelés sikertelen volt.
	 */
	public function handle()
	{
		
	}
}

?>